/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.chooser.view;

import offset.nodes.client.chooser.model.ReorderedFolder;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
import javax.swing.DropMode;
import javax.swing.ImageIcon;
import javax.swing.JTree;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeExpansionListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeNode;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeSelectionModel;
import offset.nodes.client.chooser.controller.NodeChooserActions;
import offset.nodes.client.chooser.model.FolderNode;
import offset.nodes.client.chooser.model.GetNodeTypeIcons;
import offset.nodes.client.chooser.model.NodeChooserModel;
import offset.nodes.client.chooser.model.TypeIconProvider;
import offset.nodes.client.model.CollectionServer;
import offset.nodes.client.model.NodeTypeIcon;
import offset.nodes.client.model.ResponseHandler;
import offset.nodes.client.model.Server;
import offset.nodes.client.model.ServerResponse;

/**
 * Let's select a node from the repository.
 * The chooser uses XPATH queries to determine the nodes to be displayed. It will display only nodes returned from the query and the path
 * to these nodes.
 * 
 * @author  Walter Luetgenau
 */
public class NodeChooserDialog extends javax.swing.JDialog {

    /** A return status code - returned if Cancel button has been pressed */
    public static final int RET_CANCEL = 0;
    /** A return status code - returned if OK button has been pressed */
    public static final int RET_OK = 1;
    public static final int MODE_SINGLE_SELECTION = 1;      // allow normal single selection
    public static final int MODE_MULTIPLE_SELECTION = 2;  // allow multiple selection using checkboxes
    public static final int MODE_REORDER = 4;        // allow normal multiple selection and drag & drop
    protected int mode = MODE_SINGLE_SELECTION;
    String path;
    String filter;
    Server server;
    NodeChooserModel model;
    TreeNode node = null;

    // Inner class that handles Tree Expansion Events
    protected class TreeExpansionHandler
            implements TreeExpansionListener {

        public void treeExpanded(TreeExpansionEvent evt) {
            TreePath path = evt.getPath();// The expanded path
            JTree tree =
                    (JTree) evt.getSource();// The tree

            // Get the last component of the path and
            // arrange to have it fully populated.
            LazyExpandingNode node =
                    (LazyExpandingNode) path.getLastPathComponent();
            try {
                if (node.populateChildren())
                    ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }

        public void treeCollapsed(TreeExpansionEvent evt) {
            // Nothing to do
        }
    }

    public LazyExpandingNode populatePath(LazyExpandingNode root, String pathDown) throws Exception {
        root.populateChildren();
        if (pathDown == null || pathDown.length() == 0)
            return root;

        int nextDelimiter = pathDown.indexOf("/");
        String next = null;
        String rest = null;
        if (nextDelimiter >= 0) {
            next = pathDown.substring(0, nextDelimiter);
            rest = pathDown.substring(nextDelimiter + 1);
        } else {
            next = pathDown;
            rest = null;
        }

        for (int i = 0; i < root.getChildCount(); i++) {
            LazyExpandingNode child = (LazyExpandingNode) root.getChildAt(i);
            if (child.getName().equals(next))
                return populatePath(child, rest);
        }

        return null;
    }

    /**
     * A new node chooser dialog.
     * 
     * @param parent
     * @param modal
     * @param server
     * @param path The path, from where the query starts
     * @param filter The filter, which will be executed on each expanding folder, starting from the path
     * @param onlyFolders Shall only folders (i.e. nodes with subnodes) be displayed?
     * @throws java.lang.Exception
     */
    public NodeChooserDialog(java.awt.Frame parent, boolean modal, Server server, String path, String filter) throws Exception {
        super(parent, modal);
        this.server = server;
        initComponents();
        nodeTree.setCellRenderer(NodeRendererFactory.createNodeRenderer());
        model = new NodeChooserModel(initNodeTypeIcons(server));

        if (path == null || path.length() == 0)
            path = "/";

        LazyExpandingNode root = model.initPath(path, filter);
        if (root == null) { // path was invalid
            // create tree with path referring to root
            path = "/";
            root = model.initPath(path, filter);
        }

        TreePath treePath = new TreePath(((DefaultMutableTreeNode) getRootNode(path.substring(1), root)).getPath());

        nodeTree.setModel(new DefaultTreeModel(root));
        nodeTree.setSelectionPath(treePath);
        nodeTree.scrollPathToVisible(treePath);

        pathTextField.setText(path);

        nodeTree.addTreeSelectionListener(new TreeSelectionListener() {

            public void valueChanged(TreeSelectionEvent e) {
                if (nodeTree.getSelectionCount() != 1)
                    return;
                newFolderButton.setEnabled(true);
                TreePath path = nodeTree.getSelectionPath();
                node = (TreeNode) path.getLastPathComponent();
                pathTextField.setText(getPath(node));
            }
        });

        MouseListener ml = new MouseAdapter() {

            public void mousePressed(MouseEvent e) {
                int selRow = nodeTree.getRowForLocation(e.getX(), e.getY());
                TreePath selPath = nodeTree.getPathForLocation(e.getX(), e.getY());
                if (selRow != -1)
                    if (e.getClickCount() == 2)
                        doClose(RET_OK);
            }
        };
        nodeTree.addMouseListener(ml);

        // Listen for Tree Selection Events
        nodeTree.addTreeExpansionListener(new TreeExpansionHandler());


        if (!isSingle())
            newTabCheckbox.setVisible(false);
    }
    
    /**
     * Initialize the server to send a GetNodeTypeIcons request first and 
     * to process the response.
     * 
     * This is done to enhance performance for the request, i.e. have only a single
     * HttpRequest.
     * 
     * @param server the real server
     * @return the collection server
     */
    protected Server initNodeTypeIcons(Server server) {
        if (TypeIconProvider.getInstance().getNodeTypeSize() > 0)
            return server;
        
        CollectionServer result = new CollectionServer(server);
        
        result.addRequest(new GetNodeTypeIcons.Request() , new ResponseHandler() {
            public void handleResponse(ServerResponse resp) {
                GetNodeTypeIcons.Response response = (GetNodeTypeIcons.Response) resp;

                for (NodeTypeIcon icon : response.getIcons()) {
                    TypeIconProvider.getInstance().addNodeTypeIcon(icon.getNodeType(), new ImageIcon(icon.getIcon()));
                }                
            }
        });
        
        return result;
    }

    /**
     * Constructor, that allows to set the mode.
     *
     * @param parent
     * @param modal
     * @param server
     * @param path
     * @param filter
     * @param mode
     * @throws Exception
     */
    public NodeChooserDialog(java.awt.Frame parent, boolean modal, Server server, String path, String filter, int mode) throws Exception {
        this(parent, modal, server, path, filter);
        setMode(mode);
    }

    protected boolean isReorder() {
        return (mode&MODE_REORDER) > 0;
    }

    protected boolean isMultiple() {
        return (mode&MODE_MULTIPLE_SELECTION) > 0;
    }

    protected boolean isSingle() {
        return (mode&MODE_SINGLE_SELECTION) > 0;
    }

    /**
     * Set the mode. MODE_SINGLE_SELECTION will allow normal single selection. MODE_MULTIPLE_SELECTION will
     * have a second, multiple selection using checkboxes.
     *
     * MODE_DRAG_AND_DROP sets contiguous tree selection (if not MODE_SINGLE_SELECTION is set as well) and allows
     * to reorder nodes within the same parent.
     * 
     * @param mode
     */
    protected void setMode(int mode) {
        this.mode = mode;

        if (isReorder()) {
            nodeTree.getSelectionModel().setSelectionMode(TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
            nodeTree.setDragEnabled(true);
            nodeTree.setDropMode(DropMode.INSERT);
            nodeTree.setTransferHandler(new ReorderTreeNodeTransferHandler());

            TreePath path = nodeTree.getSelectionPath();
            LazyExpandingNode selected = (LazyExpandingNode) path.getLastPathComponent();
            try {
                if (selected.populateChildren()) {
                    ((DefaultTreeModel) nodeTree.getModel()).nodeStructureChanged(selected);
                    nodeTree.expandPath(path);
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }

        }

        if (isSingle())
            nodeTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        else {
            pathLabel.setEnabled(false);
            pathTextField.setEnabled(false);
        }
    }

    /** @return the return status of this dialog - one of RET_OK or RET_CANCEL */
    public int getReturnStatus() {
        return returnStatus;
    }

    public JTree getNodeTree() {
        return nodeTree;
    }

    /**
     * Return the path of the selected node
     * 
     * @return the path
     */
    public String getPath() {
        return pathTextField.getText();
    }

    /**
     * Return the uuid of the selected node.
     *
     * @return the uuid
     */
    public String getUuid() {
        return getUuid(node);
    }

    /**
     * Return the type of the selected node.
     *
     * @return the uuid
     */
    public String getType() {
        return getType(node);
    }

   /**
     * Return the name of the selected node.
     *
     * @return the name
     */
    public String getName() {
        return getName(node);
    }

    public List<ReorderedFolder> getReorderedNodes() {
        if (!isReorder())
            return null;

        List<ReorderedFolder> result = new ArrayList<ReorderedFolder>();
        for (DefaultMutableTreeNode parent : ((ReorderTreeNodeTransferHandler) nodeTree.getTransferHandler()).getParents()) {
            ReorderedFolder movedNodes = new ReorderedFolder((FolderNode) parent.getUserObject());
            for (int i = 0; i < parent.getChildCount(); i++)
                movedNodes.addChild((FolderNode) ((DefaultMutableTreeNode) parent.getChildAt(i)).getUserObject());
            
            result.add(movedNodes);
        }

        return result;
    }

    /**
     * Return, if the user selected to use a new tab
     * to show the selected node.
     *
     * @return new tab or not?
     */
    public boolean useNewTab() {
        return newTabCheckbox.isSelected();
    }

    public String getPath(TreeNode node) {
        LinkedList list = new LinkedList();

        list.addFirst(node);
        while (node.getParent() != null) {
            node = node.getParent();
            if (node.getParent() == null) // root node
                break;
            list.addFirst(node);
        }

        StringBuffer buf = new StringBuffer();
        Iterator i = list.iterator();
        while (i.hasNext()) {
            buf.append("/");
            buf.append(i.next().toString());
        }

        return buf.toString();
    }

    /**
     * Return the uuid of the selected Node.
     *
     * @param node The selected TreeNode
     * @return the uuid
     */
    protected String getUuid(TreeNode node) {
        if (node == null)
            return null;
        if (!(node instanceof DefaultMutableTreeNode))
            return null;
        LazyExpandingNode treeNode = (LazyExpandingNode) node;
        if (treeNode.getNode() == null)
            return null;

        return treeNode.getNode().getUuid();
    }

    /**
     * Return the type of the selected Node.
     *
     * @param node The selected TreeNode
     * @return the uuid
     */
    protected String getType(TreeNode node) {
        if (node == null)
            return null;
        if (!(node instanceof DefaultMutableTreeNode))
            return null;
        LazyExpandingNode treeNode = (LazyExpandingNode) node;
        if (treeNode.getNode() == null)
            return null;

        return treeNode.getNode().getType();
    }

    /**
     * Return the name of the selected Node.
     *
     * @param node The selected TreeNode
     * @return the name
     */
    protected String getName(TreeNode node) {
        if (node == null)
            return null;
        if (!(node instanceof DefaultMutableTreeNode))
            return null;
        LazyExpandingNode treeNode = (LazyExpandingNode) node;
        if (treeNode.getNode() == null)
            return null;

        return treeNode.getNode().getName();
    }

    protected TreeNode getRootNode(String path, TreeNode root) {
        String[] names = path.split("/");
        TreeNode node = root;
        for (String name : names) {
            for (int i = 0; i < node.getChildCount(); i++) {
                if (name.equals(node.getChildAt(i).toString())) {
                    node = node.getChildAt(i);
                    break;
                }
            }
        }

        return node;
    }



    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        okButton = new javax.swing.JButton();
        cancelButton = new javax.swing.JButton();
        promptLabel = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        nodeTree = new javax.swing.JTree();
        newFolderButton = new javax.swing.JButton();
        pathLabel = new javax.swing.JLabel();
        pathTextField = new javax.swing.JTextField();
        newTabCheckbox = new javax.swing.JCheckBox();

        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("offset/nodes/client/view/resources/ClientBundle"); // NOI18N
        setTitle(bundle.getString("chooser.title")); // NOI18N
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                closeDialog(evt);
            }
        });

        okButton.setText("OK");
        okButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                selectOk(evt);
            }
        });

        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                selectCancel(evt);
            }
        });

        promptLabel.setText(bundle.getString("chooser.prompt")); // NOI18N

        nodeTree.setBorder(javax.swing.BorderFactory.createEmptyBorder(5, 5, 5, 5));
        jScrollPane1.setViewportView(nodeTree);

        newFolderButton.setAction(new NodeChooserActions.NewFolderAction(server));
        newFolderButton.setText(bundle.getString("chooser.newFolder")); // NOI18N

        pathLabel.setText(bundle.getString("chooser.nodeName")); // NOI18N

        newTabCheckbox.setText(bundle.getString("chooser.newTab")); // NOI18N
        newTabCheckbox.setToolTipText(bundle.getString("chooser.newTabDescription")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addGap(90, 90, 90)
                .addComponent(pathTextField, javax.swing.GroupLayout.DEFAULT_SIZE, 418, Short.MAX_VALUE)
                .addGap(27, 27, 27))
            .addGroup(layout.createSequentialGroup()
                .addGap(20, 20, 20)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addComponent(okButton, javax.swing.GroupLayout.PREFERRED_SIZE, 67, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(cancelButton)
                        .addGap(26, 26, 26))
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addComponent(pathLabel)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                                .addGroup(layout.createSequentialGroup()
                                    .addComponent(promptLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 140, javax.swing.GroupLayout.PREFERRED_SIZE)
                                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 174, Short.MAX_VALUE)
                                    .addComponent(newTabCheckbox)
                                    .addGap(18, 18, 18)
                                    .addComponent(newFolderButton))
                                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 488, Short.MAX_VALUE)))
                        .addGap(27, 27, 27))))
        );

        layout.linkSize(javax.swing.SwingConstants.HORIZONTAL, new java.awt.Component[] {cancelButton, okButton});

        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addGap(30, 30, 30)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(newFolderButton)
                    .addComponent(promptLabel)
                    .addComponent(newTabCheckbox))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 339, Short.MAX_VALUE)
                .addGap(41, 41, 41)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(pathTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(pathLabel))
                .addGap(19, 19, 19)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(okButton)
                    .addComponent(cancelButton))
                .addGap(23, 23, 23))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void selectOk(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectOk
        doClose(RET_OK);
    }//GEN-LAST:event_selectOk

    private void selectCancel(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_selectCancel
        doClose(RET_CANCEL);
    }//GEN-LAST:event_selectCancel

    /** Closes the dialog */
    private void closeDialog(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_closeDialog
        doClose(RET_CANCEL);
    }//GEN-LAST:event_closeDialog

    private void doClose(int retStatus) {
        returnStatus = retStatus;
        setVisible(false);
        dispose();
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton cancelButton;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton newFolderButton;
    private javax.swing.JCheckBox newTabCheckbox;
    private javax.swing.JTree nodeTree;
    private javax.swing.JButton okButton;
    private javax.swing.JLabel pathLabel;
    private javax.swing.JTextField pathTextField;
    private javax.swing.JLabel promptLabel;
    // End of variables declaration//GEN-END:variables
    private int returnStatus = RET_CANCEL;
}
